/*
 * $RCSfile: EventBaseCondition.java,v $
 *
 * Copyright  1990-2009 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */
package com.sun.perseus.model;

import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;

/**
 * An <code>EventBaseCondition</code> generates a <code>TimeInstance</code>
 * everytime the associated event happens. 
 *
 * <p>It is the responsibility of this class to register as an 
 * <code>EventListener</code>. The <code>TimeInstance</code> created
 * by an <code>EventBaseCondition</code> are cleared on reset, i.e, when
 * the associated <code>TimedElementSupport</code> restarts.
 *
 * @version $Id: EventBaseCondition.java,v 1.3 2006/06/29 10:47:31 ln156897 Exp $
 */
public class EventBaseCondition extends TimeCondition 
        implements EventListener, IDRef {
    /**
     * Offset from the event base
     */
    long offset;

    /**
     * Keeps a reference to the last occurence of the event.
     */
    Time lastEventTime = Time.UNRESOLVED;

    /**
     * The event base, i.e., the element which generates events this
     * listeners listens to.
     */
    ModelNode eventBase;

    /**
     * The id of the event base. If null, the event base is the timed
     * element of this condition.
     */
    String eventBaseId;

    /**
     * The type of event this listener listens to.
     */
    String eventType;

    /**
     * @param timedElement the associated <code>TimedElementSupport</code>. 
     *        Should not be null.
     * @param isBegin defines whether this condition is for a begin list.
     * @param eventBaseId the id of the element which generates events this
     *        listener listens to. If null, this means the events are 
     *        generated by the timedElement itself.
     * @param eventType the type of event this listener listens to. Should
     *        not be null.
     * @param offset offset from the sync base. This means that time instances
     *        synchronized on the syncBase begin or end time are offset by 
     *        this amount.
     * @throws IllegalArgumentException if eventType is null or if the 
     *         <code>ModelNode</code> associated with the 
     *         <code>timedElement</code> is null.
     */
    public EventBaseCondition(final TimedElementSupport timedElement,
                              final boolean isBegin,
                              final String eventBaseId,
                              final String eventType,
                              final long offset) {
        this(timedElement, 
             isBegin, 
             eventBaseId, 
             timedElement.animationElement, 
             eventType, 
             offset);
    }

    /**
     * @param timedElement the associated <code>TimedElementSupport</code>. 
     *        Should not be null.
     * @param isBegin defines whether this condition is for a begin list.
     * @param eventBaseId the id of the element which generates events this
     *        listener listens to. If null, this means the events are 
     *        generated by the timedElement itself.
     * @param eventBase in case eventBaseId is null, this should be used as 
     *        the source for generating events. If eventBaseId is null, this
     *        should not be null.
     * @param eventType the type of event this listener listens to. Should
     *        not be null.
     * @param offset offset from the sync base. This means that time instances
     *        synchronized on the syncBase begin or end time are offset by 
     *        this amount.
     * @throws IllegalArgumentException if eventType is null or if eventBase
     *         and eventBaseId are null.
     */
    protected EventBaseCondition(final TimedElementSupport timedElement,
                                 final boolean isBegin,
                                 final String eventBaseId,
                                 final ModelNode eventBase,
                                 final String eventType,
                                 final long offset) {
        super(timedElement, isBegin);

        if (eventType == null
            ||
            (eventBaseId == null && eventBase == null)) {
            throw new IllegalArgumentException();
        }

        this.eventBaseId = eventBaseId;
        this.eventType = eventType;
        this.offset = offset;

        ModelNode elt = timedElement.animationElement;
        if (eventBaseId == null) {
            setEventBase(eventBase);
        } else {
            elt.ownerDocument.resolveIDRef(this, eventBaseId);
        }
    }

    /**
     * Implementation helper.
     *
     * @param eventBase this node's event base.
     */
    private void setEventBase(final ModelNode eventBase) {
        if (this.eventBase != null) {
            throw new IllegalStateException();
        }

        this.eventBase = eventBase;

        // Register the listener for the bubble phase.
        eventBase.ownerDocument.getEventSupport().addEventListener
            (eventBase, eventType, EventSupport.BUBBLE_PHASE, this);
    }

    /**
     * <code>IDRef</code> implementation.
     *
     * @param ref the resolved reference (from eventBaseId).
     */
    public void resolveTo(final ElementNode ref) {
        setEventBase(ref);
    }

    /**
     * Returns the id that is referenced by this <code>IDRef</code>.
     *
     * @return the idRef.
     */
     public String getIdRef() {
         return eventBaseId;
     }
    
    /**
     * Implementation of the <code>EventListener</code> interface.
     * When an event is received from the event base, this condition
     * should generate a new <code>TimeInstance</code> that will be added
     * to the associdated <code>TimedElementSupport</code> begin or end instance
     * list (depending on the <code>isBegin</code> setting). The condition
     * should also record the time of the new event as its 
     * <code>lastEventTime</code>.
     *
     * <p>Note that a condition is sensitive to events according to the
     * SMIL Animation specification, section 3.6.4 specifying 'Event
     * Sensitivity' (or SMIL 2 Timing and Synchronization Module, 
     * 'Event Sensitivity').</p>
     *
     * @param evt the event that occured
     */
    public void handleEvent(final Event evt) {
        // =====================================================================
        // If the container is not active, there is no event sensitivity
        // If the event time is unresolved, there is no event sensitivy either,
        // as there is no way to compute the time instance that should be
        // added to the timed element.
        if (timedElement.timeContainer.state 
              != 
            TimedElementSupport.STATE_PLAYING
            ||
            !((ModelEvent) evt).eventTime.isResolved()) {
            return;
        }

        // =====================================================================
        // When the container is active, begin condition are sensitive when:
        // - there is no current interval yet, i.e., the timedElement is not
        //   active.
        // - there is a current interval, and the restart behavior is 'always'
        //
        // When the container is active (i.e., there is a resolved currentTime),
        // end condition are sensitive when the element is active and the
        // restart behavior is never or when active and the restart behavior
        // is 'always' but there is no begin condition for the event.
        Time eventTime = new Time(((ModelEvent) evt).eventTime.value);
        eventTime = timedElement.toContainerSimpleTime(eventTime);
        if (timedElement.state != TimedElementSupport.STATE_PLAYING) {
            if (isBegin) {
                new TimeInstance(timedElement, 
                                 new Time(eventTime.value + offset),
                                 true,
                                 isBegin);
                lastEventTime = eventTime;
            } 
        } else {
            // The element is active
            if (timedElement.restart == TimedElementSupport.RESTART_ALWAYS) {
                // Only add an instance if this is a begin condition
                // of if there is not begin event condition
                if (isBegin || !timedElement.hasBeginCondition(this)) {
                    new TimeInstance(timedElement, 
                                     new Time(eventTime.value + offset),
                                     true,
                                     isBegin);
                    lastEventTime = eventTime;
                }
            } else {
                if (!isBegin) {
                    new TimeInstance(timedElement, 
                                     new Time(eventTime.value + offset),
                                     true,
                                     isBegin);
                    lastEventTime = eventTime;
                }
            }
        }
    }

    /**
     * Converts this <code>EventBaseCondition</code> to a String trait.
     *
     * @return a string describing this <code>TimeCondition</code>
     */
    protected String toStringTrait() {
        StringBuffer sb = new StringBuffer();

        sb.append(eventBaseId);
        sb.append('.');
        sb.append(eventType);

        if (offset != 0) {
            if (offset > 0) {
                sb.append('+');
            } 
            sb.append(offset / 1000f);
            sb.append('s');
        }

        return sb.toString();
    }


}
